<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>二元理想混合物Gibbs能可视化</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
            color: #333;
        }
        
        .container {
            display: flex;
            max-width: 1400px;
            margin: 0 auto;
            gap: 30px;
        }
        
        .visualization {
            flex: 3;
            display: flex;
            flex-direction: row;
            gap: 30px;
        }
        
        .control-panel {
            flex: 1;
            background: white;
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
            height: fit-content;
        }
        
        .chart-container {
            background: white;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.06);
        }
        
        h1 {
            text-align: center;
            color: #2c3e50;
            margin-bottom: 30px;
            font-weight: 600;
        }
        
        h2 {
            color: #3498db;
            margin-top: 0;
            font-size: 1.4rem;
        }
        
        canvas {
            display: block;
            width: 100%;
            background: white;
            border-radius: 8px;
        }
        
        .slider-container {
            margin: 25px 0;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #2c3e50;
        }
        
        input[type="range"] {
            width: 100%;
            height: 10px;
            border-radius: 5px;
            background: #e0e7ff;
            outline: none;
            -webkit-appearance: none;
        }
        
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 22px;
            height: 22px;
            border-radius: 50%;
            background: #3498db;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
        }
        
        .input-group {
            margin-bottom: 20px;
        }
        
        .input-group input {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 8px;
            font-size: 16px;
            box-sizing: border-box;
            transition: border-color 0.3s;
        }
        
        .input-group input:focus {
            border-color: #3498db;
            outline: none;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }
        
        .values-display {
            background: #f8f9ff;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            font-size: 15px;
        }
        
        .value-item {
            display: flex;
            justify-content: space-between;
            margin-bottom: 10px;
            padding-bottom: 10px;
            border-bottom: 1px dashed #e0e6f1;
        }
        
        .value-item:last-child {
            margin-bottom: 0;
            padding-bottom: 0;
            border-bottom: none;
        }
        
        .legend {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-top: 15px;
            flex-wrap: wrap;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 14px;
        }
        
        .legend-color {
            width: 18px;
            height: 18px;
            border-radius: 3px;
        }
        
        .formula-box {
            background: #e3f2fd;
            padding: 15px;
            border-radius: 10px;
            margin: 25px 0;
            font-family: 'Courier New', monospace;
            font-size: 15px;
            line-height: 1.6;
        }
        
        @media (max-width: 900px) {
            .container {
                flex-direction: column;
            }
        }
        
        footer {
          text-align: center;
          padding: 5px;
        }
        
        /* 在CSS部分添加 */
        .colorbar-container {
            margin-bottom: 25px;
            position: relative;
        }

        .colorbar {
            height: 30px;
            width: 100%;
            border-radius: 8px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            margin-bottom: 10px;
        }

        .colorbar-labels {
            display: flex;
            justify-content: space-between;
            font-size: 13px;
            color: #555;
        }

        .colorbar-labels .label {
            padding: 2px 6px;
            border-radius: 4px;
            background: rgba(255,255,255,0.7);
        }

        .colorbar-labels .left {
            color: #3498db; /* 蓝色对应物质2 */
        }

        .colorbar-labels .right {
            color: #e74c3c; /* 红色对应物质1 */
        }

        .colorbar-labels .middle {
            position: absolute;
            top: 40px;
            left: 50%;
            transform: translateX(-50%);
            font-weight: bold;
            background: rgba(255,255,255,0.9);
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }


    </style>
</head>
<body>
    <h1>二元理想混合物Gibbs能可视化工具</h1>
    
    <div class="container">
        <div class="visualization">
            <div class="chart-container">
                <h2>📊 化学势与Gibbs能比较</h2>
                <canvas id="barChart" width="800" height="400"></canvas>
                <div class="legend">
                    <div class="legend-item">
                        <div class="legend-color" style="background-color: #e74c3c;"></div>
                        <span>物质1 (μ₁)</span>
                    </div>
                    <div class="legend-item">
                        <div class="legend-color" style="background-color: #3498db;"></div>
                        <span>物质2 (μ₂)</span>
                    </div>

                </div>
            </div>
            
            <div class="chart-container">
                <h2>📈 Gibbs能随物质比例变化曲线</h2>
                <canvas id="curveChart" width="800" height="400"></canvas>
            </div>
        </div>
        
        <div class="control-panel">
            <h2>🎛️参数控制</h2>
            
            <div class="colorbar-container">
                <div class="colorbar" id="mixtureColorbar"></div>
            </div>
            
            <div class="slider-container">
                <input type="range" id="x1Slider" min="0" max="100" value="50" step="1">
                <label for="x1Slider">🔴 物质1比例 (x₁): <span id="x1Value">0.50</span></label>
                <label for="x1Slider">🔵 物质2比例 (x₂ = 1 - x₁): <span id="x2Value">0.50</span></label>
            </div>
            
            <div class="input-group">
                <label for="mu1">🔴🧪 物质1化学势基准 μ₁* (kJ/mol):</label>
                <input type="number" id="mu1" value="10" step="0.1">
            </div>
            
            <div class="input-group">
                <label for="mu2">🔵🧪 物质2化学势基准 μ₂* (kJ/mol):</label>
                <input type="number" id="mu2" value="15" step="0.1">
            </div>
            
            <div class="input-group">
                <label for="temperature">🌡 温度 T (K):</label>
                <input type="number" id="temperature" value="300" step="10">
            </div>
            
            <div class="input-group">
                <label for="totalMoles">⚛️ 物质总量 N (mol):</label>
                <input type="number" id="totalMoles" value="1" step="0.1">
            </div>
            
            <div class="formula-box">
                <strong>🧮计算公式:</strong><br>
                G<sub>unmixed</sub> = N(x₁μ₁* + x₂μ₂*)<br>
                G<sub>mixed</sub> = N[x₁(μ₁* + RTlnx₁) + x₂(μ₂* + RTlnx₂)]<br>
                ΔG<sub>mix</sub> = G<sub>mixed</sub> - G<sub>unmixed</sub> = NRT(x₁lnx₁ + x₂lnx₂)
            </div>
            
            <div class="values-display">
                <div class="value-item">
                    <span>G<sub>unmixed</sub>:</span>
                    <span id="gUnmixed">12.50 kJ</span>
                </div>
                <div class="value-item">
                    <span>G<sub>mixed</sub>:</span>
                    <span id="gMixed">11.77 kJ</span>
                </div>
                <div class="value-item">
                    <span>ΔG<sub>mix</sub>:</span>
                    <span id="deltaG">-0.73 kJ</span>
                </div>
                <div class="value-item">
                    <span>μ₁:</span>
                    <span id="mu1Value">9.31 kJ/mol</span>
                </div>
                <div class="value-item">
                    <span>μ₂:</span>
                    <span id="mu2Value">14.31 kJ/mol</span>
                </div>
            </div>
        </div>
    </div>
    <footer>本页面使用AI创建，仅供演示</footer>
    <script>
        // 获取DOM元素
        const x1Slider = document.getElementById('x1Slider');
        const x1Value = document.getElementById('x1Value');
        const x2Value = document.getElementById('x2Value');
        const mu1Input = document.getElementById('mu1');
        const mu2Input = document.getElementById('mu2');
        const temperatureInput = document.getElementById('temperature');
        const totalMolesInput = document.getElementById('totalMoles');
        const gUnmixedDisplay = document.getElementById('gUnmixed');
        const gMixedDisplay = document.getElementById('gMixed');
        const deltaGDisplay = document.getElementById('deltaG');
        const mu1ValueDisplay = document.getElementById('mu1Value');
        const mu2ValueDisplay = document.getElementById('mu2Value');
        const colorbar = document.getElementById('mixtureColorbar');

        const barCanvas = document.getElementById('barChart');
        const curveCanvas = document.getElementById('curveChart');
        
        const barCtx = barCanvas.getContext('2d');
        const curveCtx = curveCanvas.getContext('2d');
        
        // 物理常数
        const R = 0.008314; // kJ/(mol·K)
        
        // 初始参数
        let x1 = 0.5;
        let mu1Star = 10;
        let mu2Star = 15;
        let T = 300;
        let N = 1;
        
        
        // 计算Gibbs能
        function calculateGibbs() {
            const x2 = 1 - x1;
            
            // 处理x=0的情况（避免ln(0)）
            const lnx1 = x1 > 0 ? Math.log(x1) : 0;
            const lnx2 = x2 > 0 ? Math.log(x2) : 0;
            
            // 计算化学势
            const mu1 = mu1Star + R * T * lnx1;
            const mu2 = mu2Star + R * T * lnx2;
            
            // 计算Gibbs能
            const gUnmixed = N * (x1 * mu1Star + x2 * mu2Star);
            const gMixed = N * (x1 * mu1 + x2 * mu2);
            const deltaG = gMixed - gUnmixed;
            
            return { gUnmixed, gMixed, deltaG, mu1, mu2};
        }
        
        function updateColorbar() {
            // 计算红色和蓝色的比例
            const redPercent = x1 * 100;

            // 设置渐变背景
            colorbar.style.background = `linear-gradient(90deg, 
                #e74c3c ${redPercent}%, 
                #3498db ${redPercent}%)`;
        }
        
        // 更新显示值
        function updateDisplayValues() {
            x1Value.textContent = `${Math.floor(x1*100)}%`;
            x2Value.textContent = `${100-Math.floor(x1*100)}%`;
            
            const { gUnmixed, gMixed, deltaG, mu1, mu2 } = calculateGibbs();
            
            gUnmixedDisplay.textContent = gUnmixed.toFixed(2) + " kJ";
            gMixedDisplay.textContent = gMixed.toFixed(2) + " kJ";
            deltaGDisplay.textContent = deltaG.toFixed(2) + " kJ";
            mu1ValueDisplay.textContent = mu1.toFixed(2) + " kJ/mol";
            mu2ValueDisplay.textContent = mu2.toFixed(2) + " kJ/mol";
        }
        
        // 绘制柱状图
        function drawBarChart() {
            barCtx.clearRect(0, 0, barCanvas.width, barCanvas.height);
            
            const { gUnmixed, gMixed, deltaG, mu1, mu2} = calculateGibbs();
            const x2 = 1 - x1;
            
            // 设置图表参数
            const margin = 60;
            const chartWidth = barCanvas.width - 2 * margin;
            const chartHeight = barCanvas.height - 2 * margin;
            const barWidth = 60;
            const gap = 80;
            
            // 计算最大高度用于缩放
            const maxValue = N*Math.max(mu1Star, mu2Star);
            const scaleY = chartHeight / (maxValue * 1.2);
            
            // 绘制坐标轴
            barCtx.beginPath();
            barCtx.moveTo(margin, margin + chartHeight);
            barCtx.lineTo(margin + chartWidth, margin + chartHeight);
            barCtx.moveTo(margin, margin);
            barCtx.lineTo(margin, margin + chartHeight);
            barCtx.strokeStyle = '#333';
            barCtx.lineWidth = 2;
            barCtx.stroke();
            
            // 绘制刻度
            barCtx.font = '12px Arial';
            barCtx.fillStyle = '#333';
            barCtx.textAlign = 'right';
            barCtx.textBaseline = 'middle';
            
            for (let i = 0; i <= 5; i++) {
                const y = margin + chartHeight - (i / 5) * chartHeight;
                const value = ((i / 5) * maxValue * 1.2).toFixed(1);
                
                barCtx.beginPath();
                barCtx.moveTo(margin - 5, y);
                barCtx.lineTo(margin, y);
                barCtx.stroke();
                
                barCtx.fillText(value + ' kJ', margin - 10, y);
            }
            
            // 绘制未混合状态柱状图
            const unmixedX = margin + 50;
            
            // 物质1部分
            const unmixedHeight1 = N * x1 * mu1Star * scaleY;
            barCtx.fillStyle = 'rgba(231, 76, 60, 0.7)';
            barCtx.fillRect(unmixedX, margin + chartHeight - unmixedHeight1, barWidth, unmixedHeight1);
            
            // 物质2部分
            const unmixedHeight2 = N * x2 * mu2Star * scaleY;
            barCtx.fillStyle = 'rgba(52, 152, 219, 0.7)';
            barCtx.fillRect(unmixedX, margin + chartHeight - unmixedHeight1 - unmixedHeight2, barWidth, unmixedHeight2);
            
            // 标注
            barCtx.fillStyle = '#333';
            barCtx.font = '14px Arial';
            barCtx.textAlign = 'center';
            barCtx.fillText('未混合', unmixedX + barWidth/2, margin + chartHeight + 30);
            
            // 绘制混合状态柱状图
            const mixedX = unmixedX + barWidth + gap;
            
            // 物质1部分
            const mixedHeight1 = N * x1 * mu1 * scaleY;
            barCtx.fillStyle = 'rgba(231, 76, 60, 0.7)';
            barCtx.fillRect(mixedX, margin + chartHeight - mixedHeight1, barWidth, mixedHeight1);
            
            // 物质2部分
            const mixedHeight2 = N * x2 * mu2 * scaleY;
            barCtx.fillStyle = 'rgba(52, 152, 219, 0.7)';
            barCtx.fillRect(mixedX, margin + chartHeight - mixedHeight1 - mixedHeight2, barWidth, mixedHeight2);
            
            // 标注
            barCtx.fillStyle = '#333';
            barCtx.fillText('混合', mixedX + barWidth/2, margin + chartHeight + 30);
            
            // 绘制ΔG_mix箭头
            const arrowY = margin + chartHeight - Math.max(unmixedHeight1 + unmixedHeight2, mixedHeight1 + mixedHeight2);
            barCtx.beginPath();
            barCtx.moveTo(unmixedX + barWidth + 10, arrowY);
            barCtx.lineTo(mixedX - 10, arrowY);
            barCtx.strokeStyle = '#2ecc71';
            barCtx.lineWidth = 2;
            barCtx.stroke();
            
            // 绘制箭头头部
            barCtx.beginPath();
            barCtx.moveTo(mixedX - 10, arrowY);
            barCtx.lineTo(mixedX - 20, arrowY - 5);
            barCtx.lineTo(mixedX - 20, arrowY + 5);
            barCtx.closePath();
            barCtx.fillStyle = '#2ecc71';
            barCtx.fill();
            
            // 标注ΔG_mix
            barCtx.fillStyle = '#27ae60';
            barCtx.font = 'bold 14px Arial';
            barCtx.textAlign = 'center';
            barCtx.fillText('ΔGₘᵢₓ = ' + deltaG.toFixed(2) + ' kJ', 
                           (unmixedX + mixedX + barWidth)/2, arrowY - 15);
        }
        
        // 绘制曲线图
        function drawCurveChart() {
            curveCtx.clearRect(0, 0, curveCanvas.width, curveCanvas.height);
            
            // 设置图表参数
            const margin = 60;
            const chartWidth = curveCanvas.width - 2 * margin;
            const chartHeight = curveCanvas.height - 2 * margin;
            
            // 计算曲线数据
            const points = [];
            const step = 0.01;
            
            for (let x1 = step; x1 < 1; x1 += step) {
                const x2 = 1 - x1;
                
                // 计算Gibbs能
                const lnx1 = x1 > 0 ? Math.log(x1) : 0;
                const lnx2 = x2 > 0 ? Math.log(x2) : 0;
                
                const gUnmixed = N * (x1 * mu1Star + x2 * mu2Star);
                const gMixed = N * (x1 * (mu1Star + R * T * lnx1) + x2 * (mu2Star + R * T * lnx2));
                
                points.push({ x1, gUnmixed, gMixed });
            }
            
            // 找到最大最小值用于缩放
            let maxG = -Infinity;
            let minG = Infinity;
            
            points.forEach(point => {
                if (point.gUnmixed > maxG) maxG = point.gUnmixed;
                if (point.gMixed > maxG) maxG = point.gMixed;
                if (point.gUnmixed < minG) minG = point.gUnmixed;
                if (point.gMixed < minG) minG = point.gMixed;
            });
            
            const range = maxG - minG;
            const scaleY = chartHeight / (range * 1.2);
            
            // 绘制坐标轴
            curveCtx.beginPath();
            curveCtx.moveTo(margin, margin + chartHeight);
            curveCtx.lineTo(margin + chartWidth, margin + chartHeight); // X轴
            curveCtx.moveTo(margin, margin);
            curveCtx.lineTo(margin, margin + chartHeight); // Y轴
            curveCtx.strokeStyle = '#333';
            curveCtx.lineWidth = 2;
            curveCtx.stroke();
            
            // 绘制刻度
            curveCtx.font = '12px Arial';
            curveCtx.fillStyle = '#333';
            curveCtx.textAlign = 'right';
            curveCtx.textBaseline = 'middle';
            
            // X轴刻度
            for (let i = 0; i <= 10; i++) {
                const x = margin + (i / 10) * chartWidth;
                const value = (i / 10).toFixed(1);
                
                curveCtx.beginPath();
                curveCtx.moveTo(x, margin + chartHeight + 5);
                curveCtx.lineTo(x, margin + chartHeight);
                curveCtx.stroke();
                
                curveCtx.textAlign = 'center';
                curveCtx.fillText(value, x, margin + chartHeight + 20);
            }
            
            // Y轴刻度
            curveCtx.textAlign = 'right';
            for (let i = 0; i <= 5; i++) {
                const y = margin + chartHeight - (i / 5) * chartHeight;
                const value = (minG + (i / 5) * range * 1.2).toFixed(1);
                
                curveCtx.beginPath();
                curveCtx.moveTo(margin - 5, y);
                curveCtx.lineTo(margin, y);
                curveCtx.stroke();
                
                curveCtx.fillText(value + ' kJ', margin - 10, y);
            }
            
            // 绘制未混合曲线
            curveCtx.beginPath();
            curveCtx.moveTo(margin, margin + chartHeight - (points[0].gUnmixed - minG) * scaleY);
            
            points.forEach((point, i) => {
                const x = margin + point.x1 * chartWidth;
                const y = margin + chartHeight - (point.gUnmixed - minG) * scaleY;
                
                if (i === 0) {
                    curveCtx.moveTo(x, y);
                } else {
                    curveCtx.lineTo(x, y);
                }
            });
            
            curveCtx.strokeStyle = '#e74c3c';
            curveCtx.lineWidth = 3;
            curveCtx.stroke();
            
            // 绘制混合曲线
            curveCtx.beginPath();
            curveCtx.moveTo(margin, margin + chartHeight - (points[0].gMixed - minG) * scaleY);
            
            points.forEach((point, i) => {
                const x = margin + point.x1 * chartWidth;
                const y = margin + chartHeight - (point.gMixed - minG) * scaleY;
                
                if (i === 0) {
                    curveCtx.moveTo(x, y);
                } else {
                    curveCtx.lineTo(x, y);
                }
            });
            
            curveCtx.strokeStyle = '#3498db';
            curveCtx.lineWidth = 3;
            curveCtx.stroke();
            
            // 绘制当前x1位置虚线
            const currentX = margin + x1 * chartWidth;
            curveCtx.beginPath();
            curveCtx.moveTo(currentX, margin);
            curveCtx.lineTo(currentX, margin + chartHeight);
            curveCtx.strokeStyle = '#7f8c8d';
            curveCtx.lineWidth = 1;
            curveCtx.setLineDash([5, 3]);
            curveCtx.stroke();
            curveCtx.setLineDash([]);
            
            // 绘制图例
            curveCtx.font = '14px Arial';
            curveCtx.textAlign = 'left';
            
            curveCtx.fillStyle = '#e74c3c';
            curveCtx.fillText('G_unmixed', margin + 10, margin + 30);
            
            curveCtx.fillStyle = '#3498db';
            curveCtx.fillText('G_mixed', margin + 10, margin + 60);
            
            // 标注当前点
            const { gUnmixed, gMixed } = calculateGibbs();
            const currentYUnmixed = margin + chartHeight - (gUnmixed - minG) * scaleY;
            const currentYMixed = margin + chartHeight - (gMixed - minG) * scaleY;
            
            // G_unmixed点
            curveCtx.beginPath();
            curveCtx.arc(currentX, currentYUnmixed, 6, 0, Math.PI * 2);
            curveCtx.fillStyle = '#e74c3c';
            curveCtx.fill();
            
            // G_mixed点
            curveCtx.beginPath();
            curveCtx.arc(currentX, currentYMixed, 6, 0, Math.PI * 2);
            curveCtx.fillStyle = '#3498db';
            curveCtx.fill();
            
            // 标注坐标轴
            curveCtx.fillStyle = '#333';
            curveCtx.textAlign = 'center';
            curveCtx.fillText('物质1比例 (x₁)', margin + chartWidth/2, margin + chartHeight + 40);
            
            curveCtx.save();
            curveCtx.translate(20, margin + chartHeight/2);
            curveCtx.rotate(-Math.PI/2);
            curveCtx.textAlign = 'center';
            curveCtx.fillText('Gibbs能 (kJ)', 0, 0);
            curveCtx.restore();
        }
        
        // 更新所有图表
        function updateAllCharts() {
            updateDisplayValues();
            drawBarChart();
            drawCurveChart();
            updateColorbar();
        }
        
        // 事件监听
        x1Slider.addEventListener('input', function() {
            x1 = this.value / 100;
            updateAllCharts();
        });
        
        mu1Input.addEventListener('input', function() {
            mu1Star = parseFloat(this.value) || 0;
            updateAllCharts();
        });
        
        mu2Input.addEventListener('input', function() {
            mu2Star = parseFloat(this.value) || 0;
            updateAllCharts();
        });
        
        temperatureInput.addEventListener('input', function() {
            T = parseFloat(this.value) || 300;
            updateAllCharts();
        });
        
        totalMolesInput.addEventListener('input', function() {
            N = parseFloat(this.value) || 1;
            updateAllCharts();
        });
        
        // 初始化
        updateAllCharts();
    </script>
</body>
</html>
